package com.example.framework509.common.aspects;

import com.example.framework509.common.annotation.AutoMapperProcess;
import com.example.framework509.common.annotation.Remind;
import com.example.framework509.common.annotation.entityAnnotations.Primary;
import com.example.framework509.common.annotation.entityAnnotations.Secondary;
import com.example.framework509.common.entities.MapperParam;
import com.example.framework509.common.entities.ResMsg;
import com.example.framework509.common.enums.MapperAutoOptEnum;
import com.example.framework509.common.exceptions.AnnotationException;
import com.example.framework509.common.exceptions.NotExistException;
import com.example.framework509.common.exceptions.ParamFieldException;
import com.example.framework509.common.exceptions.base.BaseCustomException;
import com.example.framework509.common.services.Impl.AutoMapperServiceImpl;
import com.example.framework509.common.services.Impl.ManyToManyAutoMapperImpl;
import com.example.framework509.common.services.Impl.OneToManyAutoMapperImpl;
import com.example.framework509.common.services.Impl.OneToOneAutoMapperImpl;
import com.example.framework509.logCore.entity.LogManage;
import com.example.framework509.logCore.entity.RequestLog;
import com.example.framework509.logCore.service.impl.LogServiceImpl;
import com.example.framework509.logCore.util.RequestUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Aspect
@Component
@Remind("对Entity命名有要求；A、B、AB 分别为主从两个实体和mapper关系" +
        "请求均为批量处理" +
        "Get  请求时 primary 赋值 查询 secondary 数据，反过来也是，注解中 ForEntity 字段决定返回的是 id 还是 实体" +
        "Post 请求中 暂时限定 entity 除了 primary 和 secondary 以外没有其他必填项，比如creator; 可以主加从，也可以从加主，但不可以 混加" +
        "Edit 请求主要处理批量更改" +
        "Del  请求 处理单条数据")
public class AutoMapperProcessAspect extends BaseAutoAspect {

    //mapper service注入
    private AutoMapperServiceImpl mapperService;
    //private EntityBaseAutoServiceImpl primaryEntityService;
    //private EntityBaseAutoServiceImpl secondaryEntityService;

    private String primaryField;
    private String secondaryField;

    //编程事务
    @Autowired
    private TransactionTemplate transactionTemplate;

    //日志相关
    @Autowired
    private LogServiceImpl logService;
    RequestLog requestLog = new RequestLog();
    LogManage logManage = new LogManage();

    //权限验证相关
    //@Autowired
    //private PermissionServiceImpl permissionService;
    //@Autowired
    //private LoginServiceImpl loginService;

    @Pointcut("@annotation(com.example.framework509.common.annotation.AutoMapperProcess)")
    public void aspectCut() {
    }

    @Around("aspectCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //日志部分起点
        long requestTime = System.currentTimeMillis();
        Object result = null;

        //获取注解信息
        AutoMapperProcess autoAnnotation = getAnnotation(pjp, AutoMapperProcess.class);
        String logMsg = autoAnnotation.LOG_MSG();
        boolean isExecute = autoAnnotation.EXECUTE();
        boolean forEntity = autoAnnotation.FOR_ENTITY();

        Object argArrObj = pjp.getArgs()[0];

        //获取请求类型
        String requestType = getOptType();
        logMsg = requestType + logMsg;

        try {
//            if (argArrObj == null || !(argArrObj instanceof ArrayList) || ((ArrayList) argArrObj).size() == 0)
//                throw new ParamFieldException();
            Object serviceImpl = getServiceImpl(argArrObj);
            ArrayList argArr = (ArrayList) argArrObj;
            this.mapperService = (AutoMapperServiceImpl) serviceImpl;
            List<String> primaryFields = getFieldsWithAnnotation(Primary.class, argArr.get(0));
            List<String> secondaryFields = getFieldsWithAnnotation(Secondary.class, argArr.get(0));
            if (primaryFields.size() != 1 || secondaryFields.size() != 1)
                throw new AnnotationException();
            this.primaryField = primaryFields.get(0);
            this.secondaryField = secondaryFields.get(0);

            if (requestType.equals("添加")) {
                Object primaryValue = getFieldValue(argArr.get(0), primaryFields.get(0));
                Object secondaryValue = getFieldValue(argArr.get(0), secondaryFields.get(0));
                if (primaryValue == null && secondaryValue != null) {
                    result = processRequest(pjp, isExecute, forEntity, MapperAutoOptEnum.GetPrimary);
                } else if (primaryValue != null && secondaryValue == null) {
                    result = processRequest(pjp, isExecute, forEntity, MapperAutoOptEnum.GetSecondary);
                } else if (primaryValue != null)
                    result = processRequest(pjp, isExecute, forEntity, MapperAutoOptEnum.CreateMapper); //为了post查询
//                    throw new ParamFieldException("参数异常");
//            } else if (requestType.equals("添加")) {
                //result = processRequest(pjp, isExecute, forEntity, MapperAutoOptEnum.CreateMapper);
            } else if (requestType.equals("修改")) {
                result = processRequest(pjp, isExecute, forEntity, MapperAutoOptEnum.FetchEdit);
            } else if (requestType.equals("删除")) {
                result = processRequest(pjp, isExecute, forEntity, MapperAutoOptEnum.DelMapper);
            }
            if (result instanceof Throwable) throw (Throwable) result;
            //日志处理
            logService.saveLog(pjp, RequestUtil.getRequest(), requestTime, result, logMsg, "无");
            return result;

        } catch (Exception e) {
            ResMsg resMsg;
            if (e instanceof BaseCustomException) {
                BaseCustomException baseCustomException = (BaseCustomException) e;
                resMsg = ResMsg.failResOneMes(baseCustomException.getMessage(), baseCustomException.getCode());
            } else {
                resMsg = ResMsg.failResOneMes(e.getMessage(), 1101);
            }
            String errorMsg = e.getMessage() == null ? "no error msg" : e.getMessage().length() > 249 ? e.getMessage().substring(0, 249) : e.getMessage();
            logService.saveLog(pjp, RequestUtil.getRequest(), requestTime, resMsg, logMsg, errorMsg);
            throw e;
        }
    }

    private Object processRequest(ProceedingJoinPoint pjp, boolean isExecute, boolean forEntity, MapperAutoOptEnum opt) throws Throwable {
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        Object execute = transactionTemplate.execute(new TransactionCallback() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {
                ResMsg resMsg = ResMsg.successRes(null);
                try {
                    Object arg = pjp.getArgs()[0];
                    Object argFirst = ((ArrayList) arg).get(0);
                    ArrayList argArr = (ArrayList) arg;
                    switch (opt) {
                        case GetPrimary:
                            int sIdForGet = Integer.parseInt(getFieldValue(argFirst, secondaryField).toString());
                            if (mapperService instanceof OneToOneAutoMapperImpl) {
                                Map<String, Object> map = new HashMap<>();
                                if (forEntity) {
                                    Object primaryEntityFromSecondaryId = ((OneToOneAutoMapperImpl) mapperService).getPrimaryEntityFromSecondaryId(sIdForGet);
                                    map.put("result", primaryEntityFromSecondaryId);
                                } else {
                                    Integer primaryIdFromSecondaryId = ((OneToOneAutoMapperImpl) mapperService).getPrimaryIdFromSecondaryId(sIdForGet);
                                    map.put("result", primaryIdFromSecondaryId);
                                }
                                resMsg = ResMsg.successRes(map);
                            } else if (mapperService instanceof OneToManyAutoMapperImpl) {
                                Map<String, Object> map = new HashMap<>();
                                if (forEntity) {
                                    Object primaryEntityFromSecondaryId = ((OneToManyAutoMapperImpl) mapperService).getPrimaryEntityFromSecondaryId(sIdForGet);
                                    map.put("result", primaryEntityFromSecondaryId);
                                } else {
                                    Integer primaryIdFromSecondaryId = ((OneToManyAutoMapperImpl) mapperService).getPrimaryIdFromSecondaryId(sIdForGet);
                                    map.put("result", primaryIdFromSecondaryId);
                                }
                                resMsg = ResMsg.successRes(map);
                            } else if (mapperService instanceof ManyToManyAutoMapperImpl) {
                                Map<String, Object> map = new HashMap<>();
                                if (forEntity) {
                                    List primaryEntityFromSecondaryId = ((ManyToManyAutoMapperImpl) mapperService).getPrimaryEntityFromSecondaryId(sIdForGet);
                                    map.put("result", primaryEntityFromSecondaryId);
                                } else {
                                    Set primaryIdsFromSecondaryId = ((ManyToManyAutoMapperImpl) mapperService).getPrimaryIdsFromSecondaryId(sIdForGet);
                                    map.put("result", primaryIdsFromSecondaryId);
                                }
                                resMsg = ResMsg.successRes(map);
                            } else {
                                throw new NotExistException("mapperService not instance of any base mapper impl");
                            }
                            break;
                        case GetSecondary:
                            int pIdForGet = Integer.parseInt(getFieldValue(argFirst, primaryField).toString());
                            if (mapperService instanceof OneToOneAutoMapperImpl) {
                                Map<String, Object> map = new HashMap<>();
                                if (forEntity) {
                                    Object primaryEntityFromSecondaryId = ((OneToOneAutoMapperImpl) mapperService).getSecondaryEntityFromPrimaryId(pIdForGet);
                                    map.put("result", primaryEntityFromSecondaryId);
                                } else {
                                    Integer primaryIdFromSecondaryId = ((OneToOneAutoMapperImpl) mapperService).getSecondaryIdFromPrimaryId(pIdForGet);
                                    map.put("result", primaryIdFromSecondaryId);
                                }
                                resMsg = ResMsg.successRes(map);
                            } else if (mapperService instanceof OneToManyAutoMapperImpl) {
                                Map<String, Object> map = new HashMap<>();
                                if (forEntity) {
                                    List secondaryEntityFromPrimaryId = ((OneToManyAutoMapperImpl) mapperService).getSecondaryEntityFromPrimaryId(pIdForGet);
                                    map.put("result", secondaryEntityFromPrimaryId);
                                } else {
                                    Set secondaryIdsFromPrimaryId = ((OneToManyAutoMapperImpl) mapperService).getSecondaryIdsFromPrimaryId(pIdForGet);
                                    map.put("result", secondaryIdsFromPrimaryId);
                                }
                                resMsg = ResMsg.successRes(map);
                            } else if (mapperService instanceof ManyToManyAutoMapperImpl) {
                                Map<String, Object> map = new HashMap<>();
                                if (forEntity) {
                                    List primaryEntityFromSecondaryId = ((ManyToManyAutoMapperImpl) mapperService).getSecondaryEntityFromPrimaryId(pIdForGet);
                                    map.put("result", primaryEntityFromSecondaryId);
                                } else {
                                    Set primaryIdsFromSecondaryId = ((ManyToManyAutoMapperImpl) mapperService).getSecondaryIdsFromPrimaryId(pIdForGet);
                                    map.put("result", primaryIdsFromSecondaryId);
                                }
                                resMsg = ResMsg.successRes(map);
                            } else {
                                throw new NotExistException("mapperService not instance of any base mapper impl");
                            }
                            break;
                        case CreateMapper:
                            Set<Integer> pIdsForCreateSet = new HashSet<>();
                            Set<Integer> sIdsForCreateSet = new HashSet<>();
                            for (int i = 0; i < argArr.size(); i++) {
                                Object argCurrentC = argArr.get(i);
                                int pIdForCreate = Integer.parseInt(getFieldValue(argCurrentC, primaryField).toString());
                                int sIdForCreate = Integer.parseInt(getFieldValue(argCurrentC, secondaryField).toString());
                                pIdsForCreateSet.add(pIdForCreate);
                                sIdsForCreateSet.add(sIdForCreate);
                            }
                            List<Integer> pIdsForCreate = new ArrayList<>(pIdsForCreateSet);
                            List<Integer> sIdsForCreate = new ArrayList<>(sIdsForCreateSet);
                            if (pIdsForCreate.size() == 1 && sIdsForCreate.size() > 1)
                                mapperService.addMapperFetch(pIdsForCreate.get(0), sIdsForCreate);
                            else if (pIdsForCreate.size() > 1 && sIdsForCreate.size() == 1)
                                mapperService.addPriMapperFetch(sIdsForCreate.get(0), pIdsForCreate);
                            else if (pIdsForCreate.size() == 1 && sIdsForCreate.size() == 1) {
                                mapperService.addMapper(new MapperParam(pIdsForCreate.get(0), sIdsForCreate.get(0)));
                            } else throw new ParamFieldException("参数异常");
                            break;
                        case FetchEdit:
                            Set<Integer> pIdsForEditSet = new HashSet<>();
                            Set<Integer> sIdsForEditSet = new HashSet<>();
                            for (int i = 0; i < argArr.size(); i++) {
                                Object argCurrentC = argArr.get(i);
                                int pIdForEdit = Integer.parseInt(getFieldValue(argCurrentC, primaryField).toString());
                                int sIdForEdit = Integer.parseInt(getFieldValue(argCurrentC, secondaryField).toString());
                                pIdsForEditSet.add(pIdForEdit);
                                sIdsForEditSet.add(sIdForEdit);
                            }
                            List<Integer> pIdsForEdit = new ArrayList<>(pIdsForEditSet);
                            List<Integer> sIdsForEdit = new ArrayList<>(sIdsForEditSet);
                            if (pIdsForEdit.size() == 1 && sIdsForEdit.size() > 1)
                                mapperService.editSecFetch(pIdsForEdit.get(0), sIdsForEdit);
                            else if (pIdsForEdit.size() > 1 && sIdsForEdit.size() == 1)
                                mapperService.editPriFetchObj(sIdsForEdit.get(0), pIdsForEdit);
                            else if (pIdsForEdit.size() == 1 && sIdsForEdit.size() == 1) {
                                mapperService.editSecFetch(pIdsForEdit.get(0), sIdsForEdit);
                            } else throw new ParamFieldException("参数异常");
                            break;
                        case DelMapper:
                            Set<Integer> pIdsForDelSet = new HashSet<>();
                            Set<Integer> sIdsForDelSet = new HashSet<>();
                            for (int i = 0; i < argArr.size(); i++) {
                                Object argCurrentC = argArr.get(i);
                                int pIdForDel = Integer.parseInt(getFieldValue(argCurrentC, primaryField).toString());
                                int sIdForDel = Integer.parseInt(getFieldValue(argCurrentC, secondaryField).toString());
                                pIdsForDelSet.add(pIdForDel);
                                sIdsForDelSet.add(sIdForDel);
                            }
                            List<Integer> pIdsForDel = new ArrayList<>(pIdsForDelSet);
                            List<Integer> sIdsForDel = new ArrayList<>(sIdsForDelSet);
                            if (pIdsForDel.size() == 1 && sIdsForDel.size() > 1)
                                mapperService.delMapperFetch(pIdsForDel.get(0), sIdsForDel);
                            else if (pIdsForDel.size() > 1 && sIdsForDel.size() == 1)
                                mapperService.delPriMapperFetch(sIdsForDel.get(0), pIdsForDel);
                            else if (pIdsForDel.size() == 1 && sIdsForDel.size() == 1) {
                                mapperService.delMapper(new MapperParam(pIdsForDel.get(0), sIdsForDel.get(0)));
                            } else throw new ParamFieldException("参数异常");
                            break;
                    }
                    if (isExecute) {
                        Object proceed = pjp.proceed();
                        if (proceed instanceof Throwable) throw (Throwable) proceed;
                    }
                    return resMsg;
                } catch (Throwable e) {
                    transactionStatus.setRollbackOnly();
                    return e;
                }
            }
        });
        if (execute instanceof Throwable) throw (Throwable) execute;
        return execute;
    }

    @Override
    protected String getOptType() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String requestType = request.getMethod();
        String opt = "";
        if (requestType.equals("GET"))
            opt = "获取";
        else if (requestType.equals("POST"))
            opt = "添加";
        else if (requestType.equals("PUT"))
            opt = "修改";
        else if (requestType.equals("DELETE"))
            opt = "删除";
        return opt;
    }

    //target removeAll data
    private List<Integer> computeSubtraction(List<Integer> target, List<Integer> data) {
        List<Integer> a = new ArrayList<>(target);
        List<Integer> b = new ArrayList<>(data);
        a.removeAll(b);
        return a;
    }

    public String toLower(String value) {
        return Character.toLowerCase(value.charAt(0)) + value.substring(1);
    }

    public String toUpper(String value) {
        return Character.toUpperCase(value.charAt(0)) + value.substring(1);
    }

    @Deprecated
    private void getEntityServiceImpl(Object entityParam, String primarySimpleName, String secondarySimpleName) {
        //实例化entity service  调用代码
        //if (forEntity || requestType.equals("修改")){
        //    String primaryFiled = toUpper(this.primaryField);
        //    String secondaryFiled = toUpper(this.secondaryField);
        //    String primarySimpleName = primaryFiled.substring(0, primaryFiled.length() - 2);
        //    String secondarySimpleName = secondaryFiled.substring(0, secondaryFiled.length() - 2);
        //    getEntityServiceImpl(arg,primarySimpleName,secondarySimpleName);
        //}

        Class<?> argClass = ((ArrayList) entityParam).get(0).getClass();
        String entityPath = argClass.getName();
        String simpleName = argClass.getSimpleName();

        String primaryPath = entityPath.replace(simpleName, primarySimpleName);
        String secondaryPath = entityPath.replace(simpleName, secondarySimpleName);

        String primaryServicePath = primaryPath.replace("entity", "service.impl")
                .replace(primarySimpleName, primarySimpleName + "ServiceImpl");

        String secondaryServicePath = secondaryPath.replace("entity", "service.impl")
                .replace(secondarySimpleName, secondarySimpleName + "ServiceImpl");

        Class<?> primaryServiceClass = null;
        Class<?> secondaryServiceClass = null;

        try {
            primaryServiceClass = Class.forName(primaryServicePath);
            secondaryServiceClass = Class.forName(secondaryServicePath);

            //this.primaryEntityService = (EntityBaseAutoServiceImpl)(applicationContext.getBean(primaryServiceClass));
            //this.secondaryEntityService = (EntityBaseAutoServiceImpl)(applicationContext.getBean(secondaryServiceClass));
        } catch (ClassNotFoundException e) {
            throw new NotExistException("servicePath" + " not exist");
        }
    }
}
